package com.dycong.common.algorithms;

import lombok.extern.log4j.Log4j;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * Created by yu on 16-9-12.
 */

@Log4j
public class ScatteredSerial {

    int X = Integer.parseInt("1011111010100010111101000101101", 2);
    //int X = Integer.parseInt("1011111010100010111101000101101", 2);

    int max;
    int length;
    boolean[] x;
    PowerOfTwoScatteredSerial[] randomSerials;
    final int deep;

    /**
     * scattered serial from 0 to max-1
     *
     * @param max
     * @param deep
     */
    public ScatteredSerial(int max, int deep, long seed) {
        X = tt(max,deep,seed);//TODO
        this.deep = deep;
        this.max = max;
        length = Integer.SIZE - Integer.numberOfLeadingZeros(max);
        x = new boolean[length];
        randomSerials = new PowerOfTwoScatteredSerial[length];
        for (int i = 0; i < length; i++) {
            int power = length - i - 1;
            x[i] = (max >> power & 1) == 1;
            if (x[i]){
                long orderIndex = 0; //TODO
                int mask = 0; //TODO
                randomSerials[i] = new PowerOfTwoScatteredSerial(power, orderIndex, mask);}
        }
    }


    public int get(int index) {
        if (index >= max)
            throw new IllegalArgumentException("index must less than max, but " + index + " >= " + max);
        return get(index, deep);
    }

    private int get(int index, int deep) {
        Integer value = null;
        PowerOfTwoScatteredSerial randomSerial = null;
        int base = 0;
        int prepareToAdd = 0;
        for (int i = 0; i < length; i++) {
            int power = length - i - 1;
            if (x[i]) {
                randomSerial = randomSerials[i];
                base += prepareToAdd;
                prepareToAdd = 1 << power;
            }
            if (index < base + prepareToAdd) {
                int subIndex = index % (1 << randomSerial.power);
                value = randomSerial.get(subIndex) + base;
//                log.info(index + " => " + value + ": " + randomSerial.get(subIndex) + " + " + base);
                break;
            }
        }
        if (deep <= 1) {
            return value;
        } else {
                int subIndex = (value + 2102089651) % max;
            return get(subIndex, deep - 1);
        }
    }


    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        for (boolean b : x)
            builder.append(b ? "1" : "0");
        return "power: " + length + ", boolean array: " + builder.toString();
    }


    public static List<Integer> getNumberOrder(long index, int size) throws IllegalArgumentException {
        if (size > 32 || index > factorial(size)) {
            throw new IllegalArgumentException("size must less than 32, and index less than factorial(size)");
        }
        List<Integer> list = IntStream.rangeClosed(1, size).boxed().collect(Collectors.toList());
        List<Integer> result = getResult(new ArrayList<>(), list, index, size);
        return result;
    }

    public static List<Integer> getResult(List<Integer> result, List<Integer> remain, long index, int size) {
        if (size == 1) {
            result.add(remain.get(0));
            return result;
        } else {
            long dg = factorial((--size));
            int i = (int) (index / dg);
            result.add(remain.remove(i));
            return getResult(result, remain, index % dg, size);
        }
    }

    public static long factorial(int base) {
        if (base < 0)
            throw new IllegalArgumentException("base must at lest 0");
        if (base <= 1) {
            return base;
        } else {
            return base * factorial(base - 1);
        }
    }


    @Log4j
    static class PowerOfTwoScatteredSerial {

        int power;
        int[] order;
        int[] not;


        public PowerOfTwoScatteredSerial(int power, long orderIndex, int mask) {
       //     log.info("new randomSerial: " + power + ", " + seed);
            this.power = power;

            this.order = new int[power];
            List<Integer> orderList= getNumberOrder(orderIndex, power);
            for(int i = 0; i < power; i++){
                this.order[i] = orderList.get(i);
            }
            if (this.power == 32) {
                this.order = new int[]{14, 20, 27, 31, 5, 19, 23, 3, 25, 21, 28, 4, 10, 18, 22, 11, 29, 2, 9, 16, 12, 6, 0, 30, 15, 8, 7, 17, 1, 26, 13, 24};
                this.not = new int[]{0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1};
            } else if (this.power == 31) {
                this.order = new int[]{14, 20, 27, 5, 19, 23, 3, 25, 21, 28, 4, 10, 18, 22, 11, 29, 2, 9, 16, 12, 6, 0, 30, 15, 8, 7, 17, 1, 26, 13, 24};
                this.not = new int[]{0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1};
            } else if (this.power == 30) {
                this.order = new int[]{10, 20, 27, 5, 19, 23, 3, 25, 21, 28, 4, 14, 18, 22, 11, 29, 2, 9, 16, 12, 6, 0, 15, 8, 7, 17, 1, 26, 13, 24};
                this.not = new int[]{0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1};
            } else if (this.power == 29) {
                this.order = new int[]{10, 20, 27, 5, 19, 23, 3, 25, 21, 28, 4, 14, 18, 22, 11, 2, 9, 16, 12, 6, 0, 15, 8, 7, 17, 1, 26, 13, 24};
                this.not = new int[]{1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1};
            } else if (this.power == 28) {
                this.order = new int[]{10, 20, 27, 5, 19, 23, 3, 25, 21, 4, 14, 18, 22, 11, 2, 9, 16, 12, 6, 0, 15, 8, 7, 17, 1, 26, 13, 24};
                this.not = new int[]{1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0};
            } else if (this.power == 27) {
                this.order = new int[]{10, 20, 5, 19, 23, 3, 25, 21, 4, 14, 18, 22, 11, 2, 9, 16, 12, 6, 0, 15, 8, 7, 17, 1, 26, 13, 24};
                this.not = new int[]{1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0};
            } else if (this.power == 26) {
                this.order = new int[]{10, 20, 5, 19, 23, 3, 25, 21, 4, 14, 18, 22, 11, 2, 9, 16, 12, 6, 0, 15, 8, 7, 17, 1, 13, 24};
                this.not = new int[]{1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0};
            } else if (this.power == 25) {
                this.order = new int[]{10, 20, 5, 19, 23, 3, 21, 4, 14, 18, 22, 11, 2, 9, 16, 12, 6, 0, 15, 8, 7, 17, 1, 13, 24};
                this.not = new int[]{1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0};
            } else if (this.power == 24) {
                this.order = new int[]{10, 20, 5, 19, 23, 3, 21, 4, 14, 18, 22, 11, 2, 9, 16, 12, 6, 0, 15, 8, 7, 17, 1, 13};
                this.not = new int[]{1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0};
            } else if (this.power == 23) {
                this.order = new int[]{10, 20, 5, 19, 3, 21, 4, 14, 18, 22, 11, 2, 9, 16, 12, 6, 0, 15, 8, 7, 17, 1, 13};
                this.not = new int[]{1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0};
            } else if (this.power == 22) {
                this.order = new int[]{10, 20, 5, 19, 3, 21, 4, 14, 18, 11, 2, 9, 16, 12, 6, 0, 15, 8, 7, 17, 1, 13};
                this.not = new int[]{1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0};
            } else if (this.power == 21) {
                this.order = new int[]{10, 20, 5, 19, 3, 4, 14, 18, 11, 2, 9, 16, 12, 6, 0, 15, 8, 7, 17, 1, 13};
                this.not = new int[]{1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0};
            } else if (this.power == 20) {
                this.order = new int[]{10, 5, 19, 3, 4, 14, 18, 11, 2, 9, 16, 12, 6, 0, 15, 8, 7, 17, 1, 13};
                this.not = new int[]{1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0};
            } else if (this.power == 19) {
                this.order = new int[]{10, 5, 3, 4, 14, 18, 11, 2, 9, 16, 12, 6, 0, 15, 8, 7, 17, 1, 13};
                this.not = new int[]{1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0};
            } else if (this.power == 18) {
                this.order = new int[]{10, 5, 3, 4, 14, 11, 2, 9, 16, 12, 6, 0, 15, 8, 7, 17, 1, 13};
                this.not = new int[]{1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0};
            } else if (this.power == 17) {
                this.order = new int[]{10, 5, 3, 4, 14, 11, 2, 9, 16, 12, 6, 0, 15, 8, 7, 1, 13};
                this.not = new int[]{1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1};
            } else if (this.power == 16) {
                this.order = new int[]{10, 5, 3, 4, 14, 11, 2, 9, 12, 6, 0, 15, 8, 7, 1, 13};
                this.not = new int[]{1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1};
            } else if (this.power == 15) {
                this.order = new int[]{10, 5, 3, 4, 14, 11, 2, 9, 12, 6, 0, 8, 7, 1, 13};
                this.not = new int[]{1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1};
            } else if (this.power == 14) {
                this.order = new int[]{10, 5, 3, 4, 11, 2, 9, 12, 6, 0, 8, 7, 1, 13};
                this.not = new int[]{1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1};
            } else if (this.power == 13) {
                this.order = new int[]{10, 5, 3, 11, 4, 2, 9, 6, 12, 0, 8, 7, 1};
                this.not = new int[]{1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0};
            } else if (this.power == 12) {
                this.order = new int[]{5, 3, 10, 4, 2, 9, 6, 11, 0, 8, 7, 1};
                this.not = new int[]{1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0};
            } else if (this.power == 11) {
                this.order = new int[]{5, 10, 3, 4, 2, 9, 6, 0, 8, 7, 1};
                this.not = new int[]{1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0};
            } else if (this.power == 10) {
                this.order = new int[]{5, 3, 4, 2, 9, 6, 0, 8, 7, 1};
                this.not = new int[]{0, 1, 0, 0, 0, 1, 0, 1, 0, 0};
            } else if (this.power == 9) {
                this.order = new int[]{5, 3, 4, 2, 6, 0, 8, 7, 1};
                this.not = new int[]{1, 0, 0, 0, 1, 0, 1, 0, 0};
            } else if (this.power == 8) {
                this.order = new int[]{5, 3, 4, 2, 6, 0, 7, 1};
                this.not = new int[]{1, 0, 1, 0, 1, 0, 1, 0};
            } else if (this.power == 7) {
                this.order = new int[]{5, 6, 3, 4, 2, 0, 1};
                this.not = new int[]{1, 0, 0, 0, 1, 0, 1};
            } else if (this.power == 6) {
                this.order = new int[]{4, 3, 1, 2, 0, 5};
                this.not = new int[]{1, 0, 1, 0, 1, 0};
            } else if (this.power == 5) {
                this.order = new int[]{3, 1, 2, 0, 4};
                this.not = new int[]{1, 0, 1, 0, 1};
            } else if (this.power == 4) {
                this.order = new int[]{3, 1, 2, 0};
                this.not = new int[]{1, 0, 1, 0};
            } else if (this.power == 3) {
                this.order = new int[]{1, 2, 0};
                this.not = new int[]{1, 1, 0};
            } else if (this.power == 2) {
                this.order = new int[]{1, 0};
                this.not = new int[]{1, 0};
            } else if (this.power == 1) {
                this.order = new int[]{0};
                this.not = new int[]{1};
            }
        }

        public int get(int index) {

            boolean[] array = new boolean[power];


            for (int i = 0; i < power; i++) {
                int x = order[i];
                array[i] = ((index >> x & 1) ^ not[i]) != 0;
            }

            int value = 0;
            for (int i = 0; i < power; i++) {
                if (array[i]) {
                    value += 1 << i;
                }
            }
            return value;
        }

    }
    public static void main(String[] args) {
        int max = 1000;
        for (int x = 2; x < 3; x++) {
            ScatteredSerial serial = new ScatteredSerial(max, x, 3333); //
            log.info(serial);
            Set<Integer> set = new HashSet<>();
            for (int i = 0; i < max; i++) {
                int value = serial.get(i);
                System.out.println(value);
                if (!set.add(value)) {
                    throw new RuntimeException(i + " => " + value + ", value exists");
                }
//                      System.out.printf("%02d => %02d\n", i, serial.get(i));
            }
            log.info(set.size());
        }


        new ScatteredSerial(max, 2, 0);
        new ScatteredSerial(max, 2, 1);
        new ScatteredSerial(max, 2, 3);



    }
    public static int tt(int max,int deep,long seed){
        int k = (int) (deep*seed%14);
        max = max<<k;
        int maxValue = Integer.MAX_VALUE;
        max = maxValue - max;
        int i = deep<<k;
        deep = (maxValue-i)|0x0F0E0A;
        max = max|deep;
        System.out.println(max);
        return max;
    }
}
